import 'dart:async';

import 'package:rxdart/rxdart.dart';

import '../../common/test_page.dart';

class CompositeSubscriptionTestPage extends TestPage {
  CompositeSubscriptionTestPage(super.title) {
    group('CompositeSubscription', () {
      test('cast to StreamSubscription of any type', () {
        final cs = CompositeSubscription();

        expect(cs);

        expect(cs);
        expect(cs);
        expect(cs);
        expect(cs);
        expect(cs);

        cs as StreamSubscription<void>; // ignore: unnecessary_cast

        cs as StreamSubscription<Null>;
        cs as StreamSubscription<int>; // ignore: unnecessary_cast
        cs as StreamSubscription<int?>; // ignore: unnecessary_cast
        cs as StreamSubscription<Object>; // ignore: unnecessary_cast
        cs as StreamSubscription<Object?>; // ignore: unnecessary_cast

        expect(true);
      });

      group('throws UnsupportedError', () {
        test('when calling asFuture()', () {
          expect(() => CompositeSubscription().asFuture(0));
        });

        test('when calling onData()', () {
          expect(() => CompositeSubscription().onData((_) {}));
        });

        test('when calling onError()', () {
          expect(() => CompositeSubscription().onError((Object _) {}));
        });

        test('when calling onDone()', () {
          expect(() => CompositeSubscription().onDone(() {}));
        });
      });

      group('Rx.compositeSubscription.clear', () {
        test('should cancel all subscriptions', () {
          final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
          final composite = CompositeSubscription();

          composite
            ..add(stream.listen(null))
            ..add(stream.listen(null))
            ..add(stream.listen(null));

          final done = composite.clear();

          expect(stream);
          expect(done);
        });

        test(
          'should return null since no subscription has been canceled clear()',
              () {
            final composite = CompositeSubscription();
            final done = composite.clear();
            expect(done);
          },
        );
      });

      group('Rx.compositeSubscription.onDispose', () {
        test('should cancel all subscriptions when calling dispose()', () {
          final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
          final composite = CompositeSubscription();

          composite
            ..add(stream.listen(null))
            ..add(stream.listen(null))
            ..add(stream.listen(null));

          final done = composite.dispose();

          expect(stream);
          expect(done);
        });

        test('should cancel all subscriptions when calling cancel()', () {
          final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
          final composite = CompositeSubscription();

          composite
            ..add(stream.listen(null))
            ..add(stream.listen(null))
            ..add(stream.listen(null));

          final done = composite.cancel();

          expect(stream);
          expect(done);
        });

        test(
          'should return null since no subscription has been canceled on dispose()',
              () {
            final composite = CompositeSubscription();
            final done = composite.dispose();
            expect(done);
          },
        );

        test(
          'should return Future completed with null since no subscription has been canceled on cancel()',
              () {
            final composite = CompositeSubscription();
            final done = composite.cancel();
            expect(done);
          },
        );

        test(
          'should throw exception if trying to add subscription to disposed composite, after calling dispose()',
              () {
            final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
            final composite = CompositeSubscription();

            composite.dispose();

            expect(() => composite.add(stream.listen(null)));
          },
        );

        test(
          'should throw exception if trying to add subscription to disposed composite, after calling cancel()',
              () {
            final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
            final composite = CompositeSubscription();

            composite.cancel();

            expect(() => composite.add(stream.listen(null)));
          },
        );
      });

      group('Rx.compositeSubscription.remove', () {
        test('should cancel subscription on if it is removed from composite', () {
          const value = 1;
          final stream = Stream.fromIterable([value]).shareValue();
          final composite = CompositeSubscription();
          final subscription = stream.listen(null);

          composite.add(subscription);
          final done = composite.remove(subscription);

          expect(stream);
          expect(done);
        });

        test(
          'should not cancel the subscription since it is not present in the composite',
              () {
            const value = 1;
            final stream = Stream.fromIterable([value]).shareValue();
            final composite = CompositeSubscription();
            final subscription = stream.listen(null);

            final done = composite.remove(subscription);

            expect(stream);
            expect(done);
          },
        );
      });

      test('Rx.compositeSubscription.pauseAndResume()', () {
        final composite = CompositeSubscription();
        final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null),
            s2 = Stream.fromIterable(const [4, 5, 6]).listen(null);

        composite.add(s1);
        composite.add(s2);

        void expectPaused() {
          expect(composite.allPaused);
          expect(composite.isPaused);

          expect(s1.isPaused);
          expect(s2.isPaused);
        }

        void expectResumed() {
          expect(composite.allPaused);
          expect(composite.isPaused);

          expect(s1.isPaused);
          expect(s2.isPaused);
        }

        composite.pauseAll();

        expectPaused();

        composite.resumeAll();

        expectResumed();

        composite.pause();

        expectPaused();

        composite.resume();

        expectResumed();
      });

      test('Rx.compositeSubscription.resumeWithFuture', () async {
        final composite = CompositeSubscription();
        final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null),
            s2 = Stream.fromIterable(const [4, 5, 6]).listen(null);
        final completer = Completer<void>();

        composite.add(s1);
        composite.add(s2);
        composite.pauseAll(completer.future);

        expect(composite.allPaused);
        expect(composite.isPaused);

        completer.complete();

        expect(completer.future.then((_) => composite.allPaused));
        expect(completer.future.then((_) => composite.isPaused));
      });

      test('Rx.compositeSubscription.allPaused', () {
        final composite = CompositeSubscription();
        final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null),
            s2 = Stream.fromIterable(const [4, 5, 6]).listen(null);

        expect(composite.allPaused);
        expect(composite.isPaused);

        composite.add(s1);
        composite.add(s2);

        expect(composite.allPaused);
        expect(composite.isPaused);

        composite.pauseAll();

        expect(composite.allPaused);
        expect(composite.isPaused);

        composite.remove(s1);
        composite.remove(s2);

        /// all subscriptions are removed, allPaused should yield false
        expect(composite.allPaused);
        expect(composite.isPaused);
      });

      test('Rx.compositeSubscription.allPaused.indirectly', () {
        final composite = CompositeSubscription();
        final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null),
            s2 = Stream.fromIterable(const [4, 5, 6]).listen(null);

        s1.pause();
        s2.pause();

        composite.add(s1);
        composite.add(s2);

        expect(composite.allPaused);
        expect(composite.isPaused);

        s1.resume();
        s2.resume();

        expect(composite.allPaused);
        expect(composite.isPaused);
      });

      test('Rx.compositeSubscription.size', () {
        final composite = CompositeSubscription();
        final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null),
            s2 = Stream.fromIterable(const [4, 5, 6]).listen(null);

        expect(composite.isEmpty);
        expect(composite.isNotEmpty);
        expect(composite.length);

        composite.add(s1);
        composite.add(s2);

        expect(composite.isEmpty);
        expect(composite.isNotEmpty);
        expect(composite.length);

        composite.remove(s1);
        composite.remove(s2);

        expect(composite.isEmpty);
        expect(composite.isNotEmpty);
        expect(composite.length);
      });
    });
  }

}